package com.safframework.server.core.handler.socket

import com.safframework.server.core.log.LogManager
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import io.netty.channel.*
import io.netty.handler.codec.http.*
import io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR
import io.netty.handler.codec.http.HttpVersion.HTTP_1_1
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory
import io.netty.util.CharsetUtil
import io.netty.util.concurrent.Future
import java.net.InetSocketAddress


/**
 *
 * @FileName:
 *          com.safframework.server.core.handler.socket.ChannelActiveHandler
 * @author: Tony Shen
 * @date: 2020-03-25 13:45
 * @version: V1.0 <描述当前版本功能>
 */
@ChannelHandler.Sharable
class ChannelActiveHandler(private val mListener: WebSocketListener<Any>) :
    SimpleChannelInboundHandler<FullHttpRequest>() {


    override fun channelRead0(ctx: ChannelHandlerContext?, msg: FullHttpRequest?) {
        try {
            handleHttpRequest(ctx, msg)
        } catch (e: Exception) {
            sendHttpResponse(ctx, msg, DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR))
        }
    }

    private fun sendHttpResponse(
        ctx: ChannelHandlerContext?,
        msg: FullHttpRequest?,
        response: DefaultFullHttpResponse
    ) {
        val statusCode: Int = response.status().code()
        if (statusCode != HttpResponseStatus.OK.code()
            && response.content().readableBytes() == 0
        ) {
            val buf: ByteBuf =
                Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8)
            response.content().writeBytes(buf)
            buf.release()
        }
        HttpUtil.setContentLength(response, response.content().readableBytes().toLong())

        // Send the response and close the connection if necessary.
        val f = ctx!!.channel().writeAndFlush(response)
        if (!HttpUtil.isKeepAlive(msg) || statusCode != HttpResponseStatus.OK.code()) {
            f.addListener(ChannelFutureListener.CLOSE)
        }
    }

    private fun handleHttpRequest(ctx: ChannelHandlerContext?, req: FullHttpRequest?) {
        val res: FullHttpResponse
        if (!req?.decoderResult()?.isSuccess!!) {
            sendHttpResponse(
                ctx,
                req,
                DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST)
            )
            return
        }
        if (req.method() != HttpMethod.GET) {
            sendHttpResponse(
                ctx,
                req,
                DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.FORBIDDEN)
            )
            return
        }
        // Handshake
        val channel: Channel = ctx!!.channel()
        val wsFactory = WebSocketServerHandshakerFactory(
            getWebSocketLocation(req),
            null,
            true,
            65536
        )
        val handshaker = wsFactory.newHandshaker(req)
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(channel)
            return
        }
        handshaker.handshake(channel, req)
            .addListener { future: Future<in Void?> ->
                if (future.isSuccess) {
                    val insocket = ctx.channel().remoteAddress() as InetSocketAddress
                    val clientIP = insocket.address.hostAddress
                    val clientPort = insocket.port

                    LogManager.i("ChannelActiveHandler", "新的连接：$clientIP : $clientPort")
                    mListener.onOpenMessage(ctx.channel(), req.headers(), initParams(req))
                } else {
                    handshaker.close(channel, CloseWebSocketFrame())
                }
            }
    }

    private fun getWebSocketLocation(req: FullHttpRequest): String? {
        val location = req.headers()[HttpHeaderNames.HOST] + req.uri()
        return "ws://$location"
    }

    private fun initParams(req: FullHttpRequest): Map<String, List<String>> {
        val paramHashValues: MutableMap<String, MutableList<String>> = LinkedHashMap()
        val index: Int = req.uri.indexOf("?")
        val params = req.uri.substring(index + 1).split("&")
        params.forEach {
            val keyValue: List<String> = it.split("=")
            if (keyValue.size != 2) {
                return@forEach
            }
            val key = keyValue[0]
            val value = keyValue[1]
            var values: MutableList<String>? =
                paramHashValues[key]
            if (values == null) {
                values = ArrayList()
                paramHashValues[key] = values
            }
            values.add(value)
        }
        return paramHashValues
    }

}